package com.flipboard.bottomsheet;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;
import ohos.utils.ObjectAttribute;

import java.util.concurrent.CopyOnWriteArraySet;

public class BottomSheetLayout extends StackLayout implements Component.TouchEventListener {
    private BindStateChangedListener attachStateListener;

    private static final ObjectAttribute<BottomSheetLayout, Float> SHEET_TRANSLATION = new ObjectAttribute<BottomSheetLayout, Float>(Float.class, "sheetTranslation") {
        @Override
        public Float get(BottomSheetLayout object) {
            return object.sheetTranslation;
        }

        @Override
        public void set(BottomSheetLayout object, Float value) {
            object.setSheetTranslation(value);
        }
    };
    private Runnable runAfterDismiss;


    /**
     * Utility class which registers if the animation has been canceled so that subclasses may respond differently in onAnimationEnd
     */
    private static class CancelDetectionAnimationListener implements Animator.StateChangedListener {

        protected boolean canceled;

        @Override
        public void onStart(Animator animator) {

        }

        @Override
        public void onStop(Animator animator) {

        }

        @Override
        public void onCancel(Animator animator) {

            canceled = true;

        }

        @Override
        public void onEnd(Animator animator) {

        }

        @Override
        public void onPause(Animator animator) {

        }

        @Override
        public void onResume(Animator animator) {

        }
    }

    private static class IdentityViewTransformer extends BaseViewTransformer {

        @Override
        public void transformView(float translation, float maxTranslation, float peekedTranslation, BottomSheetLayout parent, Component view) {
        }
    }

    public enum State {
        HIDDEN,
        PREPARING,
        PEEKED,
        EXPANDED
    }

    public interface OnSheetStateChangeListener {
        void onSheetStateChanged(State state);
    }

    private static final long ANIMATION_DURATION = 300;

    private Rect contentClipRect = new Rect();
    private State state = State.HIDDEN;
    private boolean peekOnDismiss = false;
    private int animationInterpolator = Animator.CurveType.DECELERATE;
    private float sheetTranslation;
    private VelocityDetector velocityTracker;
    private float minFlingVelocity;
    private float touchSlop;
    private ViewTransformer defaultViewTransformer = new IdentityViewTransformer();
    private ViewTransformer viewTransformer;
    private boolean shouldDimContentView = true;
    private boolean useHardwareLayerWhileAnimating = true;
    private Animator currentAnimator;
    private CopyOnWriteArraySet<OnSheetDismissedListener> onSheetDismissedListeners = new CopyOnWriteArraySet<>();
    private CopyOnWriteArraySet<OnSheetStateChangeListener> onSheetStateChangeListeners = new CopyOnWriteArraySet<>();
    private Component dimView;
    private boolean interceptContentTouch = true;
    private int currentSheetViewHeight;
    private boolean hasIntercepted;
    private float peekKeyline;
    private float peek;
    private float xDown = 0, yDown = 0;

    private float screenWidth = 0;
    //TODO resource loading
    private final boolean isTablet = false;//getResources().getBoolean(R.bool.bottomsheet_is_tablet);
    private final int defaultSheetWidth = 0;//getResources().getDimensionPixelSize(R.dimen.bottomsheet_default_sheet_width);
    private float sheetStartX = 0;
    private float sheetEndX = 0;

    public BottomSheetLayout(Context context) {
        super(context);
        init();
    }

    public BottomSheetLayout(Context context, AttrSet attrs) {
        this(context, attrs, "");
    }

    public BottomSheetLayout(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }



    private void init() {
        minFlingVelocity = 150;
        touchSlop = 24;

        dimView = new Component(getContext());
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(new RgbColor(0,0,0));
        dimView.setBackground(shapeElement);
        dimView.setAlpha(0);
        setTouchFocusable(true);
        Point point = new Point();
        screenWidth = 1176;
        sheetEndX = screenWidth;

        peek = 0; //getHeight() return 0 at start!
        peekKeyline = point.getPointY() - (screenWidth / (16.0f / 9.0f));

        attachStateListener =new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component v) {
            }

            @Override
            public void onComponentUnboundFromWindow(Component v) {

            }
        };

    }
    @Override
    public void addComponent(Component child) {
        if (getChildCount() > 0) {
            throw new IllegalArgumentException("You may not declare more then one child of bottom sheet. The sheet view must be added dynamically with showWithSheetView()");
        }
        setContentView(child);
    }
    @Override
    public void addComponent(Component child, int index, ComponentContainer.LayoutConfig params) {
        addComponent(child);
    }

    @Override
    public void addComponent(Component child, ComponentContainer.LayoutConfig params) {
        addComponent(child);
    }

    @Override
    public void addComponent(Component child, int width, int height) {
        addComponent(child);
    }


    @Override
    public boolean isBoundToWindow() {
        super.isBoundToWindow();

        velocityTracker = VelocityDetector.obtainInstance();
        return false;
    }
    private void setSheetTranslation(float newTranslation) {
        this.sheetTranslation = Math.min(newTranslation, getMaxSheetTranslation());
        int bottomClip = (int) (getHeight() - Math.ceil(sheetTranslation));
        this.contentClipRect.set(0, 0, getWidth(), bottomClip);
        getSheetView().setTranslationY(getHeight() - sheetTranslation);
        transformView(sheetTranslation);
        if (shouldDimContentView) {
            float dimAlpha = getDimAlpha(sheetTranslation);
            dimView.setAlpha(dimAlpha);
            dimView.setVisibility(dimAlpha > 0 ? VISIBLE : INVISIBLE);
        }
    }

    private void transformView(float sheetTranslation) {
        if (viewTransformer != null) {
            viewTransformer.transformView(sheetTranslation, getMaxSheetTranslation(), getPeekSheetTranslation(), this, getContentView());
        } else if (defaultViewTransformer != null) {
            defaultViewTransformer.transformView(sheetTranslation, getMaxSheetTranslation(), getPeekSheetTranslation(), this, getContentView());
        }
    }

    private float getDimAlpha(float sheetTranslation) {
        if (viewTransformer != null) {
            return viewTransformer.getDimAlpha(sheetTranslation, getMaxSheetTranslation(), getPeekSheetTranslation(), this, getContentView());
        } else if (defaultViewTransformer != null) {
            return defaultViewTransformer.getDimAlpha(sheetTranslation, getMaxSheetTranslation(), getPeekSheetTranslation(), this, getContentView());
        }
        return 0;
    }

    @Override
    public boolean onTouchEvent(Component com, TouchEvent event) {

        ComponentContainer vg = (ComponentContainer) com;
        Component child = vg.getComponentAt(2);
              switch(event.getAction()) {

                case TouchEvent.PRIMARY_POINT_DOWN:
                    xDown = event.getPointerPosition(0).getX();
                    yDown = event.getPointerPosition(0).getY();
                    break;

                case TouchEvent.POINT_MOVE:
                    float movedX, movedY;
                    movedX = event.getPointerPosition(0).getX();
                    movedY = event.getPointerPosition(0).getY();
                    float distanceX = movedX - xDown;
                    float distanceY = movedY - yDown;
                    if (child.getContentPositionX() + distanceX < -1.0) {
                        return false;
                    } else if (child.getContentPositionX() + distanceX > 1000.0) {
                        return false;
                    } else {
                        child.setContentPositionX(child.getContentPositionX()  );
                    }
                    if (child.getContentPositionY() + distanceY > 1620.0) {
                        return false;
                    }
                    if (child.getContentPositionY() + distanceY < -100.0) {
                        return false;
                    }
                    else {
                        child.setContentPositionY(child.getContentPositionY() + distanceY);
                    }
            }

      return true;
    }

    private void cancelCurrentAnimation() {
        if (currentAnimator != null) {
            currentAnimator.cancel();
        }
    }

    private boolean canScrollUp(Component view, float x, float y) {
        if (view instanceof ComponentContainer) {
            ComponentContainer vg = (ComponentContainer) view;
            for (int i = 0; i < vg.getChildCount(); i++) {
                Component child = vg.getComponentAt(i);
                int childLeft = child.getLeft() - view.getScrollValue(AXIS_X);
                int childTop = child.getTop() - view.getScrollValue(AXIS_Y);
                int childRight = child.getRight() - view.getScrollValue(AXIS_X);
                ;
                int childBottom = child.getBottom() - view.getScrollValue(AXIS_Y);
                boolean intersects = x > childLeft && x < childRight && y > childTop && y < childBottom;
                if (intersects && canScrollUp(child, x - childLeft, y - childTop)) {
                    return true;
                }
            }
        }
        return view.getScrollbarRoundRect();
    }
    private void setSheetLayerTypeIfEnabled(int layerType) {
        if (useHardwareLayerWhileAnimating) {

        }
    }

    private void setState(State state) {
        if (state != this.state) {
            this.state = state;
            for (OnSheetStateChangeListener onSheetStateChangeListener : onSheetStateChangeListeners) {
                onSheetStateChangeListener.onSheetStateChanged(state);
            }
        }
    }

    private boolean hasTallerKeylineHeightSheet() {
        return getSheetView() == null || getSheetView().getHeight() > peekKeyline;
    }

    private boolean hasFullHeightSheet() {
        return getSheetView() == null || getSheetView().getHeight() == getHeight();
    }

    /**
     * Set dim and translation to the initial state
     */
    private void initializeSheetValues() {
        this.sheetTranslation = 0;
        this.contentClipRect.set(0, 0, getWidth(), getHeight());
        getSheetView().setTranslationY(getHeight());
        dimView.setAlpha(0);
        dimView.setVisibility(INVISIBLE);
    }

    /**
     * Set the presented sheet to be in an expanded state.
     */
    public void expandSheet() {

        cancelCurrentAnimation();
        setSheetLayerTypeIfEnabled(0);
        AnimatorValue anim = new AnimatorValue();
        anim.setDuration(ANIMATION_DURATION);
        anim.setCurveType(animationInterpolator);
        anim.setStateChangedListener(new CancelDetectionAnimationListener() {
            @Override
            public void onCancel(Animator animation) {
                if (!canceled) {
                    currentAnimator = null;
                }
            }
        });
        anim.start();
        currentAnimator = anim;
        setState(State.EXPANDED);
    }

    /**
     * Set the presented sheet to be in a peeked state.
     */
    public void peekSheet() {
        cancelCurrentAnimation();
        setSheetLayerTypeIfEnabled(2);
        AnimatorValue anim = new AnimatorValue();
        anim.setDuration(ANIMATION_DURATION);
        anim.setCurveType(animationInterpolator);
        anim.setStateChangedListener(new CancelDetectionAnimationListener() {
            @Override
            public void onCancel(Animator animation) {
                if (!canceled) {
                    currentAnimator = null;
                }
            }
        });
        anim.start();
        currentAnimator = anim;
        setState(State.PEEKED);
    }
    /**
     *The peeked state translation for the presented sheet view
     * @return The peeked state translation for the presented sheet view. Translation is counted from the bottom of the view.
     */
    public float getPeekSheetTranslation() {
        return peek == 0 ? getDefaultPeekTranslation() : peek;
    }

    private float getDefaultPeekTranslation() {
        return hasTallerKeylineHeightSheet() ? peekKeyline : getSheetView().getHeight();
    }

    /**
     * Set custom height for PEEKED state.
     *
     * @param peek Peek height in pixels
     */
    public void setPeekSheetTranslation(float peek) {
        this.peek = peek;
    }
    /**
     *The maximum translation for the presented sheet view
     * @return The maximum translation for the presented sheet view. Translation is counted from the bottom of the view.
     */
    public float getMaxSheetTranslation() {
        return hasFullHeightSheet() ? getHeight() - getPaddingTop() : getSheetView().getHeight();
    }
    /**
     *he currently presented sheet view
     * @return The currently presented sheet view. If no sheet is currently presented null will returned.
     */
    public Component getContentView() {
        return getChildCount() > 0 ? getComponentAt(0) : null;
    }
    /**
     *The currently presented sheet view
     * @return The currently presented sheet view. If no sheet is currently presented null will returned.
     */
    public Component getSheetView() {
        return getChildCount() > 2 ? getComponentAt(2) : null;
    }

    /**
     * Set the content view of the bottom sheet. This is the view which is shown under the sheet
     * being presented. This is usually the root view of your application.
     *
     * @param contentView The content view of your application.
     */
    public void setContentView(Component contentView) {
        super.addComponent(contentView, -1, getLayoutConfig());
        super.addComponent(dimView, -1, getLayoutConfig());
    }

    /**
     * Convenience for showWithSheetView(sheetView, null, null).
     *
     * @param sheetView The sheet to be presented.
     */
    public void showWithSheetView(Component sheetView) {
        showWithSheetView(sheetView, null);
    }

    /**
     * Present a sheet view to the user.
     * If another sheet is currently presented, it will be dismissed, and the new sheet will be shown after that
     *
     * @param sheetView       The sheet to be presented.
     * @param viewTransformer The view transformer to use when presenting the sheet.
     */
    public void showWithSheetView(final Component sheetView, final ViewTransformer viewTransformer) {
        ComponentContainer.LayoutConfig params = (ComponentContainer.LayoutConfig) sheetView.getLayoutConfig();
            params = new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT);
            params.setMarginsTopAndBottom(500,0);
        super.addComponent(sheetView, -1, params);
        invalidate();
        setTouchEventListener(this::onTouchEvent);
    }




    /**
     * Dismiss the sheet currently being presented.
     */
    public void dismissSheet() {
        dismissSheet(null);
    }

    private void dismissSheet(Runnable runAfterDismissThis) {
        if (state == State.HIDDEN) {
            runAfterDismiss = null;
            return;
        }
        // This must be set every time, including if the parameter is null
        // Otherwise a new sheet might be shown when the caller called dismiss after a showWithSheet call, which would be
        runAfterDismiss = runAfterDismissThis;
        final Component sheetView = getSheetView();
        //sheetView.removeOnLayoutChangeListener(sheetViewOnLayoutChangeListener);
        cancelCurrentAnimation();
        AnimatorValue anim = new AnimatorValue();// = AnimatorValue.ofFloat(this, SHEET_TRANSLATION, 0);
        anim.setDuration(ANIMATION_DURATION);
        anim.setCurveType(animationInterpolator);
        anim.setStateChangedListener(new CancelDetectionAnimationListener() {
            @Override
            public void onCancel(Animator animation) {
                if (!canceled) {
                    currentAnimator = null;
                    setState(State.HIDDEN);
                    setSheetLayerTypeIfEnabled(0);
                    removeComponent(sheetView);

                    for (OnSheetDismissedListener onSheetDismissedListener : onSheetDismissedListeners) {
                        onSheetDismissedListener.onDismissed(BottomSheetLayout.this);
                    }

                    // Remove sheet specific properties
                    viewTransformer = null;
                    if (runAfterDismiss != null) {
                        runAfterDismiss.run();
                        runAfterDismiss = null;
                    }
                }
            }
        });
        anim.start();
        currentAnimator = anim;
        sheetStartX = 0;
        sheetEndX = screenWidth;
    }

    /**
     * Controls the behavior on back button press when the state is {@link State#EXPANDED}.
     *
     * @param peekOnDismiss true to show the peeked state on back press or false to completely hide
     *                      the Bottom Sheet. Default is false.
     */
    public void setPeekOnDismiss(boolean peekOnDismiss) {
        this.peekOnDismiss = peekOnDismiss;
    }

    /**
     * Returns the current peekOnDismiss value, which controls the behavior response to back presses
     * when the current state is {@link State#EXPANDED}.
     *
     * @return the current peekOnDismiss value
     */
    public boolean getPeekOnDismiss() {
        return peekOnDismiss;
    }

    /**
     * Controls whether or not child view interaction is possible when the bottomsheet is open.
     *
     * @param interceptContentTouch true to intercept content view touches or false to allow
     *                              interaction with Bottom Sheet's content view
     */
    public void setInterceptContentTouch(boolean interceptContentTouch) {
        this.interceptContentTouch = interceptContentTouch;
    }
    /**
     * Set the default view transformer to use for showing a sheet. Usually applications will use
     * a similar transformer for most use cases of bottom sheet so this is a convenience instead of
     * passing a new transformer each time a sheet is shown. This choice is overridden by any
     * view transformer passed to showWithSheetView().
     *
     * @param defaultViewTransformer The view transformer user by default.
     */
    public void setDefaultViewTransformer(ViewTransformer defaultViewTransformer) {
        this.defaultViewTransformer = defaultViewTransformer;
    }

    /**
     * Enable or disable dimming of the content view while a sheet is presented. If enabled a
     * transparent black dim is overlaid on top of the content view indicating that the sheet is the
     * foreground view. This dim is animated into place is coordination with the sheet view.
     * Defaults to true.
     *
     * @param shouldDimContentView whether or not to dim the content view.
     */
    public void setShouldDimContentView(boolean shouldDimContentView) {
        this.shouldDimContentView = shouldDimContentView;
    }
    /**
     * Enable or disable the use of a hardware layer for the presented sheet while animating.
     * This settings defaults to true and should only be changed if you know that putting the
     * sheet in a layer will negatively effect performance. One such example is if the sheet contains
     * a view which needs to frequently be re-drawn.
     *
     * @param useHardwareLayerWhileAnimating whether or not to use a hardware layer.
     */
    public void setUseHardwareLayerWhileAnimating(boolean useHardwareLayerWhileAnimating) {
        this.useHardwareLayerWhileAnimating = useHardwareLayerWhileAnimating;
    }

    /**
     * Adds an {@link OnSheetStateChangeListener} which will be notified when the state of the presented sheet changes.
     * The listener will not be automatically removed, so remember to remove it when it's no longer needed
     * (probably when the sheet is HIDDEN)
     *
     * @param onSheetStateChangeListener the listener to be notified.
     */
    public void addOnSheetStateChangeListener(OnSheetStateChangeListener onSheetStateChangeListener) {
        checkNotNull(onSheetStateChangeListener, "onSheetStateChangeListener == null");
        this.onSheetStateChangeListeners.add(onSheetStateChangeListener);
    }

    /**
     * Adds an {@link OnSheetDismissedListener} which will be notified when the state of the presented sheet changes.
     * The listener will not be automatically removed, so remember to remove it when it's no longer needed
     * (probably when the sheet is HIDDEN)
     *
     * @param onSheetDismissedListener the listener to be notified.
     */
    public void addOnSheetDismissedListener(OnSheetDismissedListener onSheetDismissedListener) {
        checkNotNull(onSheetDismissedListener, "onSheetDismissedListener == null");
        this.onSheetDismissedListeners.add(onSheetDismissedListener);
    }

    /**
     * Removes a previously added {@link OnSheetStateChangeListener}.
     *
     * @param onSheetStateChangeListener the listener to be removed.
     */
    public void removeOnSheetStateChangeListener(OnSheetStateChangeListener onSheetStateChangeListener) {
        checkNotNull(onSheetStateChangeListener, "onSheetStateChangeListener == null");
        this.onSheetStateChangeListeners.remove(onSheetStateChangeListener);
    }

    /**
     * Removes a previously added {@link OnSheetDismissedListener}.
     *
     * @param onSheetDismissedListener the listener to be removed.
     */
    public void removeOnSheetDismissedListener(OnSheetDismissedListener onSheetDismissedListener) {
        checkNotNull(onSheetDismissedListener, "onSheetDismissedListener == null");
        this.onSheetDismissedListeners.remove(onSheetDismissedListener);
    }

    /**
     * Returns whether or not BottomSheetLayout will assume it's being shown on a tablet.
     *
     * @param context Context instance to retrieve resources
     * @return True if BottomSheetLayout will assume it's being shown on a tablet, false if not
     */
    public static boolean isTablet(Context context) {
        return false;
    }
    private static <T> T checkNotNull(T value, String message) {
        if (value == null) {
            throw new NullPointerException(message);
        }
        return value;
    }

}
